package com.codefish.codefishseckill.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.codefish.codefishseckill.entity.Goods;
import com.codefish.codefishseckill.entity.Order;
import com.codefish.codefishseckill.entity.SecKillGoods;
import com.codefish.codefishseckill.entity.User;
import com.codefish.codefishseckill.exception.GlobalException;
import com.codefish.codefishseckill.mq.MQProducerService;
import com.codefish.codefishseckill.service.GoodsService;
import com.codefish.codefishseckill.service.OrderService;
import com.codefish.codefishseckill.service.SecKillGoodsService;
import com.codefish.codefishseckill.utils.SecurityUtils;
import com.codefish.codefishseckill.utils.SerialNumberUtils;
import com.codefish.codefishseckill.vo.RespBean;
import com.codefish.codefishseckill.vo.RespBeanFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpSession;
import java.util.*;

/**
 * 描述
 *
 * @author codefish
 * @version 1.0
 * @date 2022/07/09 上午 11:50
 */
@Controller
@RequestMapping("/seckill")
@Slf4j
public class SecKillController {
    @Autowired
    SecKillGoodsService secKillGoodsService;
    @Autowired
    OrderService orderService;
    @Autowired
    MQProducerService mqProducerService;
    @Autowired
    RedisTemplate<String, Object> redisTemplate;
    @Autowired
    GoodsService goodsService;
    @Autowired
    DefaultRedisScript<Long> countDownStockRedisScript;

    /**
     * 标记库存是否空了，减少redis的访问
     */
    Set<Long> emptyGoodsSet = new HashSet<>();


    /**
     * 秒杀接口v1：<br/>
     * 1. 判断商品是否存在<br/>
     * 2. 判断库存是否不足<br/>
     * 3. 判断是否已经秒杀过<br/>
     * 4. 库存减一<br/>
     * 5. 持久化订单数据<br/>
     *
     * @param goodsId  商品id
     * @param username 用户名
     * @return 秒杀接口响应
     */
    @PostMapping("/testSeckill/v1")
    @ResponseBody
    @Transactional
    public RespBean testSeckillV1(@RequestParam Long goodsId, @RequestParam Long username) {
        //  log.info("goodId:{}，username：{}", goodsId, username);
        if (username == null || goodsId == null) {
            throw new GlobalException(RespBeanFactory.denied("商品名或用户名不能为空"));
        }
        SecKillGoods goods = secKillGoodsService.getByGoodsId(goodsId);
        //商品不存在
        if (goods == null) {
            throw new GlobalException(RespBeanFactory.fail("商品" + goodsId + "不存在"));
        }

        //首先检查库存是否不足
        if (goods.getGoodsCount() <= 0) {
            throw new GlobalException(RespBeanFactory.fail("商品" + goodsId + "库存不足"));
        }

        //是否已经秒杀过
        if (secKillGoodsService.recordKillSuccessUser(goodsId, username)) {
            //抛出已秒杀异常并且回滚事务
            throw new GlobalException(RespBeanFactory.fail("商品" + goodsId + "已经被" + username + "秒杀过"));
        }

        //商品库存减一
        boolean res = secKillGoodsService.updateStockV1(goodsId, 1);
        if (!res) {
            //减库存失败,抛出异常
            throw new GlobalException(RespBeanFactory.fail("商品" + goodsId + "下单时出现未知异常"));
        }

        //添加订单到数据库
        Order order = new Order();
        order.setOrderId(SerialNumberUtils.newOrderNumber(username));
        order.setUserId(username);
        order.setGoodsId(goodsId);
        order.setGoodsName(goods.getGoodsName());
        order.setGoodsPrice(goods.getSecKillPrice());
        order.setGoodsCount(1);
        order.setOrderStatus(0);
        order.setCreateDate(new Date());

        orderService.save(order);
        return RespBeanFactory.success("秒杀成功", order);
    }


    //jmeter测试接口
    @PostMapping("/testSeckill/v2")
    @ResponseBody
    @Transactional
    public RespBean testSeckillV2(@RequestParam Long goodsId, @RequestParam Long username) {
        //   log.info("goodId:{}，username：{}", goodsId, username);
        if (username == null || goodsId == null) {
            throw new GlobalException(RespBeanFactory.denied("商品名或用户名不能为空"));
        }
        SecKillGoods goods = secKillGoodsService.getByGoodsId(goodsId);
        //商品不存在
        if (goods == null) {
            throw new GlobalException(RespBeanFactory.fail("商品" + goodsId + "不存在"));
        }

        //首先检查库存是否不足
        if (goods.getGoodsCount() <= 0) {
            throw new GlobalException(RespBeanFactory.fail("商品" + goodsId + "库存不足"));
        }
        //商品库存减一
        boolean res = secKillGoodsService.updateStockV2(goodsId, 1);

        if (!res) {
            //减库存失败，说明超卖
            throw new GlobalException(RespBeanFactory.fail("商品" + goodsId + "库存不足"));
        }

        //是否已经秒杀过
        if (secKillGoodsService.recordKillSuccessUser(goodsId, username)) {
            //抛出已秒杀异常并且回滚事务
            throw new GlobalException(RespBeanFactory.fail("商品" + goodsId + "已经被" + username + "秒杀过"));
        }


        //添加订单到数据库
        Order order = new Order();
        order.setOrderId(SerialNumberUtils.newOrderNumber(username));
        order.setUserId(username);
        order.setGoodsId(goodsId);
        order.setGoodsName(goods.getGoodsName());
        order.setGoodsPrice(goods.getSecKillPrice());
        order.setGoodsCount(1);
        order.setOrderStatus(0);
        order.setCreateDate(new Date());

        orderService.save(order);
        return RespBeanFactory.success("秒杀成功", order);
    }


    //jmeter测试接口
    @PostMapping("/testSeckill/v3")
    @ResponseBody
    @Transactional
    public RespBean testSeckillV3(@RequestParam Long goodsId, @RequestParam Long username) {
        //   log.info("goodId:{}，username：{}", goodsId, username);
        if (username == null || goodsId == null) {
            throw new GlobalException(RespBeanFactory.denied("商品名或用户名不能为空"));
        }

        //是否为空库存
        if (emptyGoodsSet.contains(goodsId)) {
            throw new GlobalException(RespBeanFactory.fail("商品" + goodsId + "库存不足"));
        }

        //查询goods信息
        SecKillGoods goods = secKillGoodsService.getByGoodsId(goodsId);


        //商品不存在
        if (goods == null) {
            throw new GlobalException(RespBeanFactory.fail("商品" + goodsId + "不存在"));
        }

        //是否已经秒杀过
        Boolean isMember = redisTemplate.opsForSet().isMember(SecKillGoodsService.SECKILL_SUCCESS_PREFIX + goodsId, username);
        if (isMember == null || isMember) {
            //抛出已秒杀异常并且回滚事务
            throw new GlobalException(RespBeanFactory.fail("商品" + goodsId + "已经被" + username + "秒杀过"));
        }

        //秒杀，从redis中预减库存
        Long res = redisTemplate.opsForValue().decrement(SecKillGoodsService.SECKILL_GOODS_STOCK_PREFIX + goodsId);
        if (res == null || res < 0) {
            //库存小于0，超卖
            redisTemplate.opsForValue().increment(SecKillGoodsService.SECKILL_GOODS_STOCK_PREFIX + goodsId);
            //标记空库存
            emptyGoodsSet.add(goodsId);
            throw new GlobalException(RespBeanFactory.fail("商品" + goodsId + "库存不足"));
        }

        //redis中添加秒杀成功的用户id
        redisTemplate.opsForSet().add(SecKillGoodsService.SECKILL_SUCCESS_PREFIX + goodsId, username);

        //添加订单到数据库
        Order order = new Order();
        order.setOrderId(SerialNumberUtils.newOrderNumber(username));
        order.setUserId(username);
        order.setGoodsId(goodsId);
        order.setGoodsName(goods.getGoodsName());
        order.setGoodsPrice(goods.getSecKillPrice());
        order.setGoodsCount(1);
        order.setOrderStatus(0);
        order.setCreateDate(new Date());

        //发送订单消息到mq
        mqProducerService.senderOrder(order);

        return RespBeanFactory.success("秒杀成功", order);
    }


    @PreAuthorize("@ps.hasPermission(#goodsId)")
    @PostMapping("/{path}/doSeckill")
    @Transactional
    public String doSeckill(@RequestParam Long goodsId, Model model, HttpSession session, @PathVariable(name = "path") String path
            , @RequestParam String captcha) {

        User userInfo = SecurityUtils.getLocalUser().getUser();
        String captchaToken = String.valueOf(userInfo.getUsername());
        log.info("{}", userInfo);

        //校验path是否正确
        boolean isPathLegal = secKillGoodsService.checkPath(path, userInfo, goodsId);
        if (!isPathLegal) {
            model.addAttribute("errmsg", "地址不合法");
            return "secKillFail";
        }

        //校验验证码输入是否正确
        String realCaptcha = (String) redisTemplate.opsForValue().get(ResourceController.CAPTCHA_REDIS_PREFIX + captchaToken);
        if (StringUtils.isEmpty(realCaptcha) || !realCaptcha.equalsIgnoreCase(captcha)) {
            model.addAttribute("errmsg", "验证码错误");
            return "secKillFail";
        }


        SecKillGoods goods = secKillGoodsService.getByGoodsId(goodsId);

        //商品不存在
        if (goods == null) {
            model.addAttribute("errmsg", "商品" + goodsId + "不存在");
            return "secKillFail";
        }

        //借助redis判断是否已经秒杀过
        Boolean resx = redisTemplate.opsForSet().isMember(SecKillGoodsService.SECKILL_SUCCESS_PREFIX + goodsId, userInfo.getUsername());
        if (resx == null || resx) {
            model.addAttribute("errmsg", "商品" + goodsId + "已经被您秒杀过");
            return "secKillFail";
        }

        if (emptyGoodsSet.contains(goodsId)) {
            model.addAttribute("errmsg", "秒杀商品" + goodsId + "失败，库存不足");
            return "secKillFail";
        }

        //秒杀，redis中预减库存

//   Long res = redisTemplate.opsForValue().decrement(SecKillGoodsService.SECKILL_GOODS_STOCK_PREFIX + goodsId);
//        if (res == null || res < 0) {
//            //减库存后库存数小于0，说明商品商品不足
//            emptyGoodsSet.add(goodsId);
//            redisTemplate.opsForValue().increment(SecKillGoodsService.SECKILL_GOODS_STOCK_PREFIX + goodsId);
//            model.addAttribute("errmsg", "秒杀商品" + goodsId + "失败，库存不足");
//            return "secKillFail";
//        }

        // 通过lua脚本来实现减库存
        Long res = redisTemplate.execute(countDownStockRedisScript, Collections.singletonList(SecKillGoodsService.SECKILL_GOODS_STOCK_PREFIX + goodsId), Collections.EMPTY_LIST);
        if (res == null || res == 0) {
            model.addAttribute("errmsg", "秒杀商品" + goodsId + "失败，库存不足");
            return "secKillFail";
        }
        log.info("商品{}剩余{}件", goodsId, res);


        //添加秒杀成功名单到redis
        redisTemplate.opsForSet().add(SecKillGoodsService.SECKILL_SUCCESS_PREFIX + goodsId, userInfo.getUsername());


        // 生成订单
        Order order = new Order();
        order.setOrderId(SerialNumberUtils.newOrderNumber(userInfo.getUsername()));
        order.setUserId(userInfo.getUsername());
        order.setGoodsId(goodsId);
        order.setGoodsName(goods.getGoodsName());
        order.setGoodsPrice(goods.getSecKillPrice());
        order.setGoodsCount(1);
        order.setOrderStatus(0);
        order.setCreateDate(new Date());

        //发送订单消息到mq
        mqProducerService.senderOrder(order);


        model.addAttribute("goods", goods);


        return "secKillWaiting";
    }

    //获取秒杀结果
    @PostMapping("/getResult")
    @ResponseBody
    public RespBean getResult(@RequestParam Long goodsId) {
        User userInfo = SecurityUtils.getLocalUser().getUser();
        Long orderId = orderService.getOneByCondition(userInfo.getUsername(), goodsId);
        return RespBeanFactory.success("秒杀结果查询成功", orderId);
    }

    //跳转秒杀订单详情页
    @GetMapping("/getOrder")
    public String getOrder(@RequestParam Long orderId, Model model) {
        Order order = orderService.getOrderByOrderId(orderId);
        QueryWrapper<Goods> wrapper = new QueryWrapper<>();
        wrapper.eq("goods_id", order.getGoodsId());
        Goods goods = goodsService.getOne(wrapper);
        model.addAttribute("order", order);
        model.addAttribute("goods", goods);

        return "orderDetail";
    }


    /**
     * 库存预读取到redis，bean初始化时调用
     */
    @PostConstruct
    public void afterPropertiesSet() {
        log.info("预加载商品库存在redis中....");
        List<SecKillGoods> killGoods = secKillGoodsService.list();
        killGoods.forEach(goods -> {
            redisTemplate.opsForValue().set(SecKillGoodsService.SECKILL_GOODS_STOCK_PREFIX + goods.getGoodsId(), goods.getGoodsCount());
        });
        log.info("商品库存预加载完成，共{}条记录....", killGoods.size());
    }
}
